<?php

/**
 * Quickie parser class that can happily read the subset of PHP we need
 * for our localization arrays safely.
 *
 * About an order of magnitude faster than ConfEditor(), but still an
 * order of magnitude slower than eval().
 */
class QuickArrayReader {
	var $vars = array();

	/**
	 * @param $string string
	 */
	function __construct( $string ) {
		$scalarTypes = array(
			T_LNUMBER => true,
			T_DNUMBER => true,
			T_STRING => true,
			T_CONSTANT_ENCAPSED_STRING => true,
		);
		$skipTypes = array(
			T_WHITESPACE => true,
			T_COMMENT => true,
			T_DOC_COMMENT => true,
		);
		$tokens = token_get_all( $string );
		$count = count( $tokens );
		for( $i = 0; $i < $count; ) {
			while( isset($skipTypes[$tokens[$i][0]] ) ) {
				$i++;
			}
			switch( $tokens[$i][0] ) {
			case T_OPEN_TAG:
				$i++;
				continue;
			case T_VARIABLE:
				// '$messages' -> 'messages'
				$varname = trim( substr( $tokens[$i][1], 1 ) );
				$varindex = null;

				while( isset($skipTypes[$tokens[++$i][0]] ) );

				if( $tokens[$i] === '[' ) {
					while( isset($skipTypes[$tokens[++$i][0]] ) );

					if( isset($scalarTypes[$tokens[$i][0]] ) ) {
						$varindex = $this->parseScalar( $tokens[$i] );
					} else {
						throw $this->except( $tokens[$i], 'scalar index' );
					}
					while( isset($skipTypes[$tokens[++$i][0]] ) );

					if( $tokens[$i] !== ']' ) {
						throw $this->except( $tokens[$i], ']' );
					}
					while( isset($skipTypes[$tokens[++$i][0]] ) );
				}

				if( $tokens[$i] !== '=' ) {
					throw $this->except( $tokens[$i], '=' );
				}
				while( isset($skipTypes[$tokens[++$i][0]] ) );

				if( isset($scalarTypes[$tokens[$i][0]] ) ) {
					$buildval = $this->parseScalar( $tokens[$i] );
				} elseif( $tokens[$i][0] === T_ARRAY ) {
					while( isset($skipTypes[$tokens[++$i][0]] ) );
					if( $tokens[$i] !== '(' ) {
						throw $this->except( $tokens[$i], '(' );
					}
					$buildval = array();
					do {
						while( isset($skipTypes[$tokens[++$i][0]] ) );

						if( $tokens[$i] === ')' ) {
							break;
						}
						if( isset($scalarTypes[$tokens[$i][0]] ) ) {
							$key = $this->parseScalar( $tokens[$i] );
						}
						while( isset($skipTypes[$tokens[++$i][0]] ) );

						if( $tokens[$i][0] !== T_DOUBLE_ARROW ) {
							throw $this->except( $tokens[$i], '=>' );
						}
						while( isset($skipTypes[$tokens[++$i][0]] ) );

						if( isset($scalarTypes[$tokens[$i][0]] ) ) {
							$val = $this->parseScalar( $tokens[$i] );
						}
						@$buildval[$key] = $val;
						while( isset($skipTypes[$tokens[++$i][0]] ) );

						if( $tokens[$i] === ',' ) {
							continue;
						} elseif( $tokens[$i] === ')' ) {
							break;
						} else {
							throw $this->except( $tokens[$i], ', or )' );
						}
					} while(true);
				} else {
					throw $this->except( $tokens[$i], 'scalar or array' );
				}
				if( is_null( $varindex ) ) {
					$this->vars[$varname] = $buildval;
				} else {
					@$this->vars[$varname][$varindex] = $buildval;
				}
				while( isset($skipTypes[$tokens[++$i][0]] ) );
				if( $tokens[$i] !== ';' ) {
					throw $this->except($tokens[$i], ';');
				}
				$i++;
				break;
			default:
				throw $this->except($tokens[$i], 'open tag, whitespace, or variable.');
			}
		}
	}

	/**
	 * @param $got string
	 * @param $expected string
	 * @return Exception
	 */
	private function except( $got, $expected ) {
		if( is_array( $got ) ) {
			$got = token_name( $got[0] ) . " ('" . $got[1] . "')";
		} else {
			$got = "'" . $got . "'";
		}
		return new Exception( "Expected $expected, got $got" );
	}

	/**
	 * Parse a scalar value in PHP
	 *
	 * @param $token string
	 *
	 * @return mixed Parsed value
	 */
	function parseScalar( $token ) {
		if( is_array( $token ) ) {
			$str = $token[1];
		} else {
			$str = $token;
		}
		if ( $str !== '' && $str[0] == '\'' )
			// Single-quoted string
			// @fixme trim() call is due to mystery bug where whitespace gets
			// appended to the token; without it we ended up reading in the
			// extra quote on the end!
			return strtr( substr( trim( $str ), 1, -1 ),
				array( '\\\'' => '\'', '\\\\' => '\\' ) );
		if ( $str !== '' && @$str[0] == '"' )
			// Double-quoted string
			// @fixme trim() call is due to mystery bug where whitespace gets
			// appended to the token; without it we ended up reading in the
			// extra quote on the end!
			return stripcslashes( substr( trim( $str ), 1, -1 ) );
		if ( substr( $str, 0, 4 ) === 'true' )
			return true;
		if ( substr( $str, 0, 5 ) === 'false' )
			return false;
		if ( substr( $str, 0, 4 ) === 'null' )
			return null;
		// Must be some kind of numeric value, so let PHP's weak typing
		// be useful for a change
		return $str;
	}

	/**
	 * @param $varname string
	 * @return null|string
	 */
	function getVar( $varname ) {
		if( isset( $this->vars[$varname] ) ) {
			return $this->vars[$varname];
		} else {
			return null;
		}
	}
}

